/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.brillien.services.jsonrpc;

import com.vii.brillien.core.management.BrillienServices;
import com.vii.brillien.ignition.BrillienContext;
import com.vii.brillien.ignition.transport.BrillienCommunication;
import com.vii.brillien.ignition.transport.BrillienMediator;
import com.vii.brillien.kernel.BrillienException;
import com.vii.brillien.kernel.axiom.sso.Session;
import com.vii.streamline.json.JsonRpcListener;
import com.vii.streamline.json.JsonRpcRequest;
import com.vii.streamline.json.JsonRpcResponse;
import com.vii.streamline.services.error.StreamLineException;
import com.vii.streamline.services.reflection.Invocation;
import com.vii.streamline.structures.collections.InnerMap;
import org.slf4j.Logger;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static com.vii.streamline.json.JsonRpcServices.ERROR_INTERNAL_ERROR;
import static com.vii.streamline.json.JsonRpcServices.ERROR_METHOD_NOT_FOUND;

/**
 * Specialized JsonRpcListener implementation using a mediator instance.
 */
public class BrillienJsonRpcListener extends JsonRpcListener {

    protected BrillienMediator          mediator;

    public BrillienJsonRpcListener(boolean ssoRequired, Logger logger, BrillienMediator mediator) {
        super(ssoRequired, logger);
        this.mediator   = mediator;
    }

    @Override
    protected boolean isSsoRequired(){
        return  BrillienServices.liaison != null
                ? BrillienServices.liaison.getSSOPresenceName() != null
                : super.isSsoRequired();
    }

    @Override
    public boolean isMethodReckognised(Invocation invocation) {
        return BrillienServices.isServiceExists( invocation );
    }

    @Override
    public boolean hasMessageProcessorReturnValue( Invocation invocation ) throws StreamLineException{
        try{
            Boolean res = mediator.sendGet(
                    Session.createSession( invocation.getSessionId() ),
                    invocation.getEntity(),
                    "hasMessageProcessorReturnValue",
                    new InnerMap<String, Object>( "messageProcessorName", invocation.getMethod() )
            ).getResponse();
            return res;
        } catch (BrillienException e) {
            throw new StreamLineException( e );
        }
    }

    @Override
    public List<String> getParameterNames(Invocation invocation) throws StreamLineException {
        try{
            List<String> res = mediator.sendGet(
                    Session.createSession( invocation.getSessionId() ),
                    invocation.getEntity(),
                    "getMessageProcessorParameterNames",
                    new InnerMap<String, Object>( "messageProcessorName", invocation.getMethod() )
            ).getResponse();
            return res;
        } catch (BrillienException e) {
            throw new StreamLineException( e );
        }
    }

    @Override
    public List<Class> getParameterTypes(Invocation invocation) throws StreamLineException {
        try{
            List<String> res = mediator.sendGet(
                    Session.createSession( invocation.getSessionId() ),
                    invocation.getEntity(),
                    "getMessageProcessorParameterTypes",
                    new InnerMap<String, Object>( "messageProcessorName", invocation.getMethod() )
            ).getResponse();
            LinkedList<Class> result = new LinkedList<Class>();
            for( String classname : res ){
                Class c = null;

                if( c == null )
                    try{
                        c = BrillienServices.getPresenceManagerByName( BrillienServices.liaison, invocation.getEntity() ).getClassLoader().loadClass( classname );
                    } catch(Exception e){ }

                if( c == null )
                    try{
                        c = BrillienContext.primordialClassLoader.loadClass(classname);
                    } catch(Exception e){ }

                if( c == null )
                    try{
                        c = mediator.getClass().getClassLoader().loadClass( classname );
                    } catch(Exception e){ }

                if( c == null )
                    try{
                        c = Class.forName(classname, false, mediator.getClass().getClassLoader());
                    } catch(Exception e){ }

                if( c == null )
                    throw new StreamLineException("Cannot load class: " + classname );

                result.add( c );
            }
            return result;
        } catch (Exception e) {
            throw new StreamLineException( e );
        }
    }

    @Override
    public Map<String, Class> getParameterMap(Invocation invocation) throws StreamLineException {
        if( !isMethodReckognised( invocation ) )
            throw new StreamLineException("Invocation's target cannot be identified.");

        List<String> names  = getParameterNames( invocation );
        List<Class> types   = getParameterTypes( invocation );

        Map<String,Class> res = new HashMap<String,Class>();

        for( int i=0; i<names.size(); ++i )
            res.put(
                    names.get(i),
                    types.get(i)
            );

        return res;
    }

    @Override
    protected JsonRpcResponse call(String sessionId, JsonRpcRequest typisedRequest) {
        logger.debug( BrillienJsonRpcServlet.class.getName() + " has been called with " + typisedRequest + " in session: " + sessionId );

        Invocation invocation = new Invocation( typisedRequest.getSessionId(), typisedRequest.getMethod() );

        if( !isMethodReckognised( invocation ) )
            return JsonRpcResponse.createResponse( typisedRequest, ERROR_METHOD_NOT_FOUND.getCode(), ERROR_METHOD_NOT_FOUND.getMessage(), "Method " + typisedRequest.getMethod() + " cannot be recognised!" );
            //throw new StreamLineException("Method " + typisedRequest.getMethod() + " cannot be recognised!");

        Session session = isSsoRequired() ? Session.createSession(sessionId) : null;

        try {
            HashMap<String,Object> map = (HashMap<String,Object>)typisedRequest.getParams();

            logger.debug(
                "Calling Presence " +
                invocation.getEntity() + "via " +
                invocation.getMethod() + " using: " + map
            );

            boolean hasReturnValue = hasMessageProcessorReturnValue( invocation );

            Object payload = null;

            if( hasReturnValue ){
                BrillienCommunication c = mediator.sendGet(
                        session, BrillienContext.RECONNECTION_TIMEOUT,
                        invocation.getEntity(),
                        invocation.getMethod(),
                        map
                );
                payload = c.getResponse();
            } else {
                mediator.sendSet(
                        session,
                        invocation.getEntity(),
                        invocation.getMethod(),
                        map
                );
                payload = "Success.";
            }

            JsonRpcResponse response = JsonRpcResponse.createResponse( typisedRequest, payload );

            logger.debug(
                "Response received: " + response
            );

            return response;
        } catch (BrillienException e) {
            logger.error( e.getMessage(), e );
            return JsonRpcResponse.createResponse( typisedRequest, e.getErrorCode() == -1 ? ERROR_INTERNAL_ERROR.getCode() : e.getErrorCode(), e.getMessage(), e.getValue() );
        } catch (Exception e) {
            logger.error( e.getMessage(), e );
            return JsonRpcResponse.createResponse( typisedRequest, ERROR_INTERNAL_ERROR.getCode(), ERROR_INTERNAL_ERROR.getMessage(), e.getMessage() );
        }
    }

}
